Дефиниция на виртуален
базов клас
Един базов клас се определя като виртуален чрез добавянето
на клю-човата дума virtual към неговата декларация. Например, следващата
декларация прави ZooAnimal виртуален базов клас на Bear и Raccoon.
//
placement order of keywords public
// and virtual is not significant
class
Bear : public virtual ZooAnimal { /* ... */ }
class Raccoon : virtual public
ZooAnimal { /* ... */ }
Ако един виртуален базов клас дефинира
конструктор, конструкторът не трябва да изисква списък от аргументи - това
трябва да бъде конструктор без аргументи или конструктор, за който е определена
стойност по премълчаване за всеки един от аргументите му. Освен това, няма нужда
да бъде променяна дефиницията на ZooAnimal, за да се определи виртуалния базов
клас. Ето дефиницията на ZooAnimal, с която ще работим, дискутирайки виртуалните
базови класове: Това изискване вече е било премахвано веднъж от езика, макар че
AT&T 2.0 версията на С++ го въвежда. Според сегашните правила, ако се
изисква конструктор по подразбиране, но такъв няма, се предизвиква грешка по
време на компилация. По-ранното ограничение е било въведено за да предпазва от
грешка по време на компилация.
class ZooAnimal { // simplified
definition
public:
ZooAnimal() { zooArea = 0; name = 0;
}
ZooAnimal( char*, short );
void locate();
protected:
short
zooArea;
char *name; };
По същия начин се използват обектите от
класовете Bear и Raccoon. Един виртуален базов клас се инициализира както не
виртуален базов клас:
Bear::Bear( char *nm ): ZooAnimal( nm, BEAR ) { ...
}
Raccoon::Raccoon( char *nm )
: ZooAnimal( nm, RACCOON ) { ...
}
Декларацията на Panda изглежда по същия начин както нейния не виртуален
образец:
class Panda : public Bear, public Raccoon { ... };
Тъй
като и Bear, и Raccoon декларират ZooAnimal като виртуален базов клас, всеки
обект на Panda вече ще съдържа само един образец на ZooAnimal. Обикновено един
извлечен клас може явно да инициализира само преките си базови класове.
Например, в не виртуално извличане от ZooAnimal Panda може да не вика по име
ZooAnimal в списъка за инициализация на конструктора си. Виртуалните базови
класове са едно изключение. Ще обясним защо. Panda съдържа единствен образец на
ZooAnimal, споделян от Raccoon и Bear. Raccoon и Bear, обаче, едновременно явно
инициализират ZooAnimal. Образец на Panda не може да бъде инициализиран
двукратно. Един виртуален базов клас се инициализира от класа, който е в
действителност извличан. Например, не Raccoon или Bear, а Panda е в
действителност извлечен от ZooAnimal. Panda може явно да инициализира ZooAnimal
в списъка за инициализация на своя конструктор. Ако конструкторът на Panda не
инициализира явно ZooAnimal, извиква се конструкторът по подразбиране на
ZooAnimal. Никога не се прави инициализиране на ZooAnimal част в обект от типа
Panda чрез Raccoon и Bear.
Конструкторът на Panda може да бъде дефиниран
по следния начин:
Panda::Panda( char *nm ): ZooAnimal( nm, PANDA ),Bear(
nm ), Raccoon( nm ) { ... }
Panda::Panda( char *nm ): Bear( nm ), Raccoon( nm
) { ... }
извиква конструктора по подразбиране на
ZooAnimal.
Достъп до членовете на виртуален базов
клас
Всеки обект от класовете Raccoon и Bear поддържа свое собствено
множество от членове на ZooAnimal, което може да бъде достигнато точно по същия
начин както унаследените членове от не виртуални извличания. При едно отделно
преглеждане на програмния код не може да се направи разлика между употребата на
обект на извличане от виртуален и на не виртуален базов клас; единствената
разлика е в начина на отделянето на частта от обекта, съответна на базовия
клас.
При не виртуално извличане всеки извлечен обект от класа съдържа
долепващи се базова и извлечена част на класа (виж Фигура 7.4 ). Обект от класа
Bear, например, има базова част ZooAnimal и Bear част. При виртуално извличане
всеки обект от извлечения клас съдържа извлечена част и указател към частта,
съответна на виртуалния базов клас. Виртуалният базов клас не се съдържа в
обекта от извлечения клас. Това е илюстрирано за класовете Bear, Raccoon и Panda
на Фигура 8.3.Един виртуален базов клас се подчинява на същите правила за public
и private, както и при не виртуално извличане:
- унаследените членове при
public виртуално извличане запазват нивото си на достъп - public или protected -
в извлечения клас.
- унаследените членове при private виртуално извличане
стават private членове на извлечения клас.
Какво става, когато
еднократно-отдалечено извличане, такова като Panda, включва едновременно public
и private образци на един виртуален базов клас? Например,
class
ZooAnimal
{public:
void locate();
protected:
short
zooArea;
};
class Bear :
public virtual ZooAnimal { /* ... */
};
class Raccoon :
private virtual ZooAnimal { /* ... */ };
class Panda
:
public Bear, public Raccoon { /* ... */ };
class Raccoon : public
virtual ZooAnimal
class Bear : public virtual ZooAnimal
Фигура 8.3
Представяне на виртуален базов клас
Panda наследява само едно копие
на zooArea и locate(), защото ZooAnimal е виртуален базов клас. Въпросът е дали
на Panda е разрешен достъпа до тези членове? При public онаследяване Panda има
достъп до тези два члена на ZooAnimal. Обаче, те не са достъпни за Panda при
private онаследяване. Кой вид онаследяване се взема в предвид при виртуално
извличане? Вярно ли е следното обръщение?
Panda p;p.locate();
Да,
обръщението е вярно. Взема се в предвид public онаследяването. Panda има достъп
до zooArea и locate(). Като цяло, независимо от брояча образците на виртуалните
базови класове в йерархията на онаследяването, ако съществува отделен public
образец, един споделен образец при извличане се разглежда като
public.
ZooAnimal дефинира член функция locate(), която Raccoon
наследява. Обръщението
Bear b;
b.locate(); // Bear
instance
вика образеца на Bear, a не този на ZooAnimal. При не виртуално
извличане обръщението
Panda p;
p.locate(); // which locate? e
двузначно, ако Panda не дефинира свой собствен образец на locate(). Ако обаче
ZooAnimal е виртуален базов клас едновременно за Raccoon и Bear, се вика
образеца locate() на Bear. Двузначността е премахната. При виртуално извличане
се взема същинският извлечен образец на член функцията. Затова се взема образеца
locate() на Bear, а не този на ZooAnimal, наследен в Raccoon.